package edu.drexel.jrexel2d;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import edu.drexel.jrexel2d.camera.Camera;
import edu.drexel.jrexel2d.collision.Collision;
import edu.drexel.jrexel2d.collision.Detector;
import edu.drexel.jrexel2d.gamestate.Level;
import edu.drexel.jrexel2d.input.GameAction;
import edu.drexel.jrexel2d.input.GameInput;
import edu.drexel.jrexel2d.input.InputEngine;
import edu.drexel.jrexel2d.networking.GamePacket;
import edu.drexel.jrexel2d.networking.NetworkServer;
import edu.drexel.jrexel2d.networking.PlayerNetworkInfo;
import edu.drexel.jrexel2d.networking.Serializer;
import edu.drexel.jrexel2d.networking.socket.GameServerSocket;
import edu.drexel.jrexel2d.networking.sync.ServerGameUpdate;
import edu.drexel.jrexel2d.objects2D.Player;
import edu.drexel.jrexel2d.physics.PhysicsEngine;
import edu.drexel.jrexel2d.rendering.RenderingEngine;

public class jrexel2d_server {

	private final int gameHertz = 60;
	private final long skipTicks = 1000 / gameHertz;
	private final int frameSkip = 5;
	private final int maxPlayers = 2;

	private final int displayWidth = 800;
	private final int displayHeight = 600;

	/** frames per second */
	private int fps;
	/** last fps time */
	private long lastTPS;
	private long lastTick;

	private int delta;

	private long nextGameTick;

	private NetworkServer networkServer;
	GamePacket recievedPacket;

	private List<PlayerNetworkInfo> players;
	private List<GameServerSocket> establishedClients;
	private GameInput gameInput;

	private InputEngine inputEngine;

	private Level curLevel;

	private PhysicsEngine physicsEngine;

	private ArrayList<Collision> cols;

	private Camera camera;

	private RenderingEngine renderingEngine;

	// Map of time and playerId
	private Map<Integer, Long> timeOfPlayerUpdates;

	public jrexel2d_server() {
		networkServer = new NetworkServer(1835, 1024);
		players = new ArrayList<PlayerNetworkInfo>();
		gameInput = new GameInput();
		inputEngine = new InputEngine();
		physicsEngine = new PhysicsEngine();
		camera = new Camera();
		// This shouldnt be here, but is necessary for now to get the server
		// processing the level object.
		renderingEngine = new RenderingEngine();
		timeOfPlayerUpdates = new HashMap<Integer, Long>();

	}

	public void start() {
		nextGameTick = getTime();
		lastTPS = getTime(); // call before loop to initialize fps timer

		// curLevel.setPlayerPhysicsEngine(physicsEngine);

		// Necessary for processing level object. This needs to be
		// fixed////////////
		try {
			Display.setDisplayMode(new DisplayMode(displayWidth, displayHeight));
			Display.setInitialBackground(.5f, .5f, .5f);
			Display.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
			System.exit(0);
		}
		renderingEngine.initGL(); // init OpenGL
		// /////////////////////////////////////////////////////////////////////////////

		curLevel = new Level("level1");
		physicsEngine.init(curLevel);
		curLevel.setObjectPhysicsEngine(physicsEngine);

		gameLoop();

	}

	public void gameLoop() {

		while (true) {
			long currentTime = getTime();
			while (getTime() > nextGameTick) {
				/* GameInput gameInput = getNetworkUpdates(); */
				nextGameTick = nextGameTick + skipTicks;
				gameInput = getNetworkUpdates();
				/*
				 * if (players.size()<2){ addPlayersToGame(gameInputList); }
				 */

				delta = updateDelta();

				processGameActions();
				updateCollisionModule();

				updatePhysicsModule(delta);

				sendNetworkUpdates();
				updateTPS(); // update FPS Counter
			}
		}

	}

	private void processGameActions() {
		// this code takes the user input and updates the position of the
		// player.
		// TODO: this code needs a better implementation
		List<Player> players = curLevel.getPlayers();
		List<GameAction> gameActions = gameInput.getGameActions();
		for (GameAction ga : gameActions) {

			//this can be better optimized
			for (Player player : players) {
				if (ga.getCharacterId() == player.getPlayerId()) {
					inputEngine.executeGameAction(ga, player);

					updateCollisionModule();

					updatePhysicsModule(delta);

					camera.updateCamera(curLevel, displayWidth, displayHeight);
					timeOfPlayerUpdates.put(player.getPlayerId(), ga.getTime());

				}

			}
		}

	}

	public GameInput getNetworkUpdates() {
		GameInput gameInput = new GameInput();
		// networkServer.acceptConnection();
		List<GamePacket> gamePackets = new ArrayList<GamePacket>();
		if (!networkServer.acceptConnectionsAndRecievePackets(gamePackets))
			return gameInput;
		// establishedClients = networkServer.getEstablishedClients();
		// recievedPacket = networkServer.receivePacket();
		// byte[] data = networkServer.getData();

		for (GamePacket packet : gamePackets) {
			GameInput recievedInput = (GameInput) Serializer
					.deserializeBytes(packet.getData());
			if (recievedInput.getGameActions().size() > 0) {
				System.out.println("Recieved game input from player: ");
			}

			recievedInput = returnValidGameActions(recievedInput);
			gameInput.getGameActions().addAll(recievedInput.getGameActions());
		}
		players = networkServer.getEstablishedClients();
		return gameInput;
	}

	private GameInput returnValidGameActions(GameInput gameInput) {
		/*
		 * List<GameAction> gameActions = new ArrayList<GameAction>();
		 * gameActions = gameInput.getGameActions(); for (GameAction ga :
		 * gameActions) { if (!isValidGameAction(ga)) { gameActions.remove(ga);
		 * } }
		 */
		return gameInput;
	}

	private boolean isValidGameAction(GameAction ga) {
		// TODO Auto-generated method stub
		return true;
	}

	public void updateCollisionModule() {
		Detector d = new Detector(curLevel);

		cols = d.check();
	}

	public void updatePhysicsModule(int delta) {

		physicsEngine.gameLoopUpdates(cols, delta);

	}

	public void sendNetworkUpdates() {

		// send input to sender
		// networkServer.sendSerializable(gameInput, recievedPacket.getPort() ,
		// recievedPacket.getAddress());

		// send input to all players
		for (PlayerNetworkInfo pni : players) {
			/*
			 * System.out.println("Sending game input to player: " +
			 * pni.getPlayerId());
			 */
			/*
			 * networkServer.sendSerializable(curLevel, pni.getPort(),
			 * pni.getIp(), pni.getServerSocket());
			 */

			ServerGameUpdate serverGameUpdate = new ServerGameUpdate(
					curLevel.getObjectPointMap(), timeOfPlayerUpdates, curLevel
							.getBackdrop().getLocation());
			networkServer.sendSerializable((serverGameUpdate), pni.getPort(),
					pni.getIp(), pni.getServerSocket());

		}
		timeOfPlayerUpdates = new HashMap<Integer, Long>();

	}

	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
		return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}

	/**
	 * Calculate the FPS and output it to the console every second
	 */
	public void updateTPS() {

		if (getTime() - lastTPS > 1000) {
			System.out.println("fps:" + fps);
			fps = 0;
			lastTPS += 1000;
		}

		fps++;
	}

	public int updateDelta() {

		long time = getTime();
		int delta = (int) (time - lastTick);

		lastTick = time;

		return delta;
	}

}